#include "rm_test_util.h"
#include <random>

RC TEST_RM_PRIVATE_2(const std::string &tableName) {
    // Functions tested
    // 1. Insert 100000 Tuples - with null values
    // 2. Read Attribute
    std::cout << std::endl << "***** In RM Test Case Private 2 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    int numTuples = 100000;
    void *tuple;
    void *returnedData = malloc(300);

    std::vector<RID> rids;
    std::vector<char *> tuples;
    RC rc = 0;

    // GetAttributes
    std::vector<Attribute> attrs;
    rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    auto *nullsIndicatorWithNulls = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);
    memset(nullsIndicatorWithNulls, 0, nullAttributesIndicatorActualSize);
    nullsIndicatorWithNulls[0] = 172;   // 10101100- the 1st, 3rd, 5th, and 6th columns are null.
    // - userid, sender_location, referred_topics, and message_text

    for (int i = 0; i < numTuples; i++) {
        tuple = malloc(300);

        // Insert Tuple
        auto sender_location = (float) i;
        float send_time = (float) i + 2000;
        int tweetid = i;
        int userid = i;
        std::stringstream ss;
        ss << std::setw(5) << std::setfill('0') << i;
        std::string msg = "Msg" + ss.str();
        std::string referred_topics = "Rto" + ss.str();

        // There will be some tuples with nulls.
        if (i % 35 == 0) {
            prepareTweetTuple(attrs.size(), nullsIndicatorWithNulls, tweetid, userid, sender_location, send_time,
                              referred_topics.size(), referred_topics, msg.size(), msg, tuple, &tupleSize);
        } else {
            prepareTweetTuple(attrs.size(), nullsIndicator, tweetid, userid, sender_location, send_time,
                              referred_topics.size(), referred_topics, msg.size(), msg, tuple, &tupleSize);
        }

        rc = rm.insertTuple(tableName, tuple, rid);
        assert(rc == success && "RelationManager::insertTuple() should not fail.");

        tuples.push_back((char *) tuple);
        rids.push_back(rid);

        if (i % 10000 == 0) {
            std::cout << (i + 1) << "/" << numTuples << " records have been inserted so far." << std::endl;
        }
    }
    std::cout << "All records have been inserted." << std::endl;

    std::cout << "Start record checking..." << std::endl;

    bool testFail = false;
    bool nullBit;
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_real_distribution<double> dist(0, 6);
    std::uniform_real_distribution<double> dist2(0, 4);

    for (int i = 0; i < numTuples; i++) {
        int attrID = dist(mt);
        std::string attributeName;

        // Force attrID to be the ID that contains NULL when a i % 35 is 0.
        if (i % 35 == 0) {
            int k = dist2(mt);
            attrID = (k % 4 == 0) ? 0 : (k % 4 == 1) ? 2 : (k % 4 == 2) ? 4 : 5;

        }

        if (attrID == 0) {
            attributeName = "tweetid";
        } else if (attrID == 1) {
            attributeName = "userid";
        } else if (attrID == 2) {
            attributeName = "sender_location";
        } else if (attrID == 3) {
            attributeName = "send_time";
        } else if (attrID == 4) {
            attributeName = "referred_topics";
        } else if (attrID == 5) {
            attributeName = "message_text";
        }
        rc = rm.readAttribute(tableName, rids[i], attributeName, returnedData);
        assert(rc == success && "RelationManager::readAttribute() should not fail.");

        // NULL indicator should say that a NULL value is returned.
        if (i % 35 == 0) {
            nullBit = *(unsigned char *) ((char *) returnedData) & ((unsigned) 1 << (unsigned) 7);
            if (!nullBit) {
                std::cout << "A returned value from a readAttribute() is not correct: attrID - " << attrID << std::endl;
                testFail = true;
            }
        } else {
            int value = 0;
            float fvalue = 0;
            std::stringstream ss;
            ss << std::setw(5) << std::setfill('0') << i;
            std::string msgToCheck = "Msg" + ss.str();
            std::string referred_topicsToCheck = "Rto" + ss.str();

            // tweetid
            if (attrID == 0) {
                if (memcmp(((char *) returnedData + 1), ((char *) tuples.at(i) + 1), 4) != 0) {
                    testFail = true;
                } else {
                    value = *(int *) ((char *) returnedData + 1);
                    if (value != i) {
                        testFail = true;
                    }
                }
                // userid
            } else if (attrID == 1) {
                if (memcmp(((char *) returnedData + 1), ((char *) tuples.at(i) + 5), 4) != 0) {
                    testFail = true;
                } else {
                    value = *(int *) ((char *) returnedData + 1);
                    if (value != i) {
                        testFail = true;
                    }
                }
                // sender_location
            } else if (attrID == 2) {
                if (memcmp(((char *) returnedData + 1), ((char *) tuples.at(i) + 9), 4) != 0) {
                    testFail = true;
                } else {
                    fvalue = *(float *) ((char *) returnedData + 1);
                    if (fvalue != (float) i) {
                        testFail = true;
                    }
                }
                // send_time
            } else if (attrID == 3) {
                if (memcmp(((char *) returnedData + 1), ((char *) tuples.at(i) + 13), 4) != 0) {
                    testFail = true;
                }
                // referred_topics
            } else if (attrID == 4) {
                if (memcmp(((char *) returnedData + 5), ((char *) tuples.at(i) + 21), 8) != 0) {
                    testFail = true;
                } else {
                    std::string strToCheck(((char *) returnedData + 5), 8);
                    if (strToCheck != referred_topicsToCheck) {
                        testFail = true;
                    }
                }
                // message_text
            } else if (attrID == 5) {
                if (memcmp(((char *) returnedData + 5), ((char *) tuples.at(i) + 33), 8) != 0) {
                    testFail = true;
                } else {
                    std::string strToCheck(((char *) returnedData + 5), 8);
                    if (strToCheck != msgToCheck) {
                        testFail = true;
                    }
                }
            }
        }

        if (testFail) {
            std::cout << "***** RM Test Case Private 2 failed on the tuple #" << i << " - attrID: " << attrID << "*****"
                      << std::endl << std::endl;
            free(returnedData);
            for (int j = 0; j < numTuples; j++) {
                free(tuples[j]);
            }
            rm.deleteTable(tableName);

            return -1;
        }

    }

    free(returnedData);
    for (int i = 0; i < numTuples; i++) {
        free(tuples[i]);
    }

    std::cout << "***** RM Test Case Private 2 finished. The result will be examined. *****" << std::endl << std::endl;

    return 0;
}

int main() {
    createTweetTable("tbl_private_2");
    return TEST_RM_PRIVATE_2("tbl_private_2");
}
